Skip to content

Conversation

@abhinavkrin
Copy link
Member

@abhinavkrin abhinavkrin commented Jan 19, 2026

Proposed changes (including videos or screenshots)

This PR fixes an issue where the Contact Manager field could not be cleared (set to empty) for a contact, either via the UI or through the API.

The PR brings changes which will allow clearing the contactManager field by broviding an empty string for the 'contactManager' field in the payload for the endpoint omnichannel/contacts.update.

Issue(s)

Steps to test or reproduce

Further comments

SUP-954

Summary by CodeRabbit

  • Bug Fixes

    • Endpoints now allow clearing the contact manager field when an empty or missing value is provided (applies to update and conflict-resolution flows).
    • Improved error handling when a contact cannot be found during update/conflict resolution.
  • Tests

    • Added tests validating removal of contact manager via update and during conflict resolution, plus expanded conflict-resolution scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@abhinavkrin abhinavkrin requested review from a team as code owners January 19, 2026 19:43
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 19, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.2.0, but it targets 8.1.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jan 19, 2026

🦋 Changeset detected

Latest commit: acd1f78

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 40 packages
Name Type
@rocket.chat/models Patch
@rocket.chat/meteor Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/ui-client Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/media-calls Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/network-broker Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/ui-voip Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/license Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Walkthrough

Replaces the contact update flow with a patch-style API (patchContact) that accepts { set?, unset? }, enabling explicit unsetting of fields (notably contactManager); updates models, typings, server handlers, tests, and adds a changeset.

Changes

Cohort / File(s) Summary
Changeset
\.changeset/thick-ties-hunt.md
Adds changeset noting patch bumps for @rocket.chat/models and @rocket.chat/meteor and documents fixes allowing clearing contactManager.
Model typings
packages/model-typings/src/models/ILivechatContactsModel.ts
Replaces updateContact(...) signature with patchContact(contactId, { set?: Partial<ILivechatContact>, unset?: Array<keyof ILivechatContact> }, options?) returning `ILivechatContact
Models implementation
packages/models/src/models/LivechatContacts.ts
Adds patchContact that accepts { set, unset }; no-op when empty; constructs $set (includes unknown, derives preRegistration) and conditional $unset; performs findOneAndUpdate and returns updated contact or null.
Server handlers
apps/meteor/app/livechat/server/lib/contacts/updateContact.ts, apps/meteor/app/livechat/server/lib/contacts/resolveContactConflicts.ts, apps/meteor/app/livechat/server/lib/contacts/patchContact.ts
Switch callers to use patchContact; build { set, unset } payloads (presence check for contactManager to decide unsetting); throw error-contact-not-found if patch returns null; add server helper patchContact.
Server unit tests
apps/meteor/app/livechat/server/lib/contacts/*.spec.ts
Replace updateContact stubs/usages with patchContact; update assertions to new payload shape; add tests for clearing contactManager, conflict resolution paths, and related error cases.
End-to-end tests
apps/meteor/tests/end-to-end/api/livechat/contacts.ts
Adds E2E tests verifying clearing contactManager via omnichannel/contacts.update and during omnichannel/contacts.conflicts, asserting the field is removed.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (UI/API)
    participant Server as Omnichannel API
    participant Model as LivechatContacts.model
    participant DB as MongoDB
    participant Post as Post-update flows

    rect rgba(200,220,255,0.5)
    Client->>Server: POST omnichannel/contacts.update (contactId, contactManager: "")
    end

    rect rgba(200,255,200,0.5)
    Server->>Model: patchContact(contactId, { set: {...}, unset?: ['contactManager'] })
    Model->>DB: findOneAndUpdate({ _id, enabled: { $ne: false } }, { $set, $unset? })
    DB-->>Model: updatedContact | null
    Model-->>Server: updatedContact | null
    end

    rect rgba(255,230,200,0.5)
    Server->>Post: trigger name/room/subscription/inquiry updates using updatedContact
    Server-->>Client: HTTP 200 { success: true, contact: updatedContact }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • KevLehman
  • tassoevan

Poem

🐰 I hopped to the DB with a little tap,
Switched updates to patches — a neat little map.
I unset the manager, set fields just right,
Contacts now clear in the soft moonlight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: enabling the ability to clear the contact manager field, which directly addresses the primary changeset objective.
Linked Issues check ✅ Passed The PR implementation successfully addresses SUP-954 by allowing the contactManager field to be cleared via empty string in the omnichannel/contacts.update and omnichannel/contacts.conflicts endpoints.
Out of Scope Changes check ✅ Passed All changes are focused on implementing the patch-based contact update mechanism to support clearing the contactManager field, with no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/clear-contact-manager-field

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@abhinavkrin abhinavkrin changed the title Fix/clear contact manager field fix: unable to clear contact manager field Jan 19, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/models/src/models/LivechatContacts.ts (1)

117-133: Remove the inline comment on line 122 to follow coding guidelines.

Per the coding guidelines for TypeScript files, avoid code comments in implementation. The comment explaining the contactManager unsetting intent is unnecessary—the code logic is clear from the conditional check and operation.

The MongoDB behavior regarding empty $unset objects is handled correctly for the driver versions in use (6.10.0+). However, the main issue is the inline comment that violates the stated guideline.

🧹 Nitpick comments (1)
apps/meteor/app/livechat/server/lib/contacts/updateContact.ts (1)

74-82: Logic is correct for enabling contactManager clearing.

The use of 'contactManager' in params correctly detects key presence regardless of value, enabling the model layer to handle empty strings as unset operations. The validation at lines 39-41 appropriately only triggers for truthy values.

However, as per coding guidelines for TypeScript files, consider removing the inline comment on line 78. The behavior is self-documenting through the 'in' operator usage, and the model layer's handling can be documented in the model itself if needed.

🔧 Suggested change
 	const updatedContact = await LivechatContacts.updateContact(contactId, {
 		name,
 		...(emails && { emails: emails?.map((address) => ({ address })) }),
 		...(phones && { phones: phones?.map((phoneNumber) => ({ phoneNumber })) }),
-		...('contactManager' in params && { contactManager }), // A contactManager of empty string or undefined will be unset in the model method
+		...('contactManager' in params && { contactManager }),
 		...(channels && { channels }),
 		...(customFieldsToUpdate && { customFields: customFieldsToUpdate }),
 		...(wipeConflicts && { conflictingFields: [] }),
 	});

@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 90.24390% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.89%. Comparing base (75d089c) to head (acd1f78).
⚠️ Report is 5 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38265      +/-   ##
===========================================
+ Coverage    70.76%   70.89%   +0.13%     
===========================================
  Files         3159     3161       +2     
  Lines       109397   109813     +416     
  Branches     19657    19729      +72     
===========================================
+ Hits         77416    77855     +439     
+ Misses       29950    29939      -11     
+ Partials      2031     2019      -12     
Flag Coverage Δ
e2e 60.35% <ø> (+0.04%) ⬆️
e2e-api 47.85% <ø> (-0.09%) ⬇️
unit 72.11% <90.24%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2026

📦 Docker Image Size Report

➡️ Changes

Service Current Baseline Change Percent
sum of all images 0B 0B 0B
account-service 0B 0B 0B
authorization-service 0B 0B 0B
ddp-streamer-service 0B 0B 0B
omnichannel-transcript-service 0B 0B 0B
presence-service 0B 0B 0B
queue-worker-service 0B 0B 0B
rocketchat 0B 0B 0B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/18 22:53", "11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "01/28 16:57 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.00]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "queue-worker-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "rocketchat" [0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.00]
Loading

Statistics (last 30 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.4GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 0B
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-38265
  • Baseline: develop
  • Timestamp: 2026-01-28 16:57:21 UTC
  • Historical data points: 30

Updated: Wed, 28 Jan 2026 16:57:22 GMT

Copy link
Contributor

@ricardogarim ricardogarim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, might the same issue exist in resolveContactConflicts.ts for the omnichannel/contacts.conflicts endpoint? It also uses a truthy check which would prevent clearing the contactManager field.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/app/livechat/server/lib/contacts/resolveContactConflicts.ts (1)

46-69: Contact-manager conflicts won’t clear when contactManager is emptied.

Clearing contactManager now uses unset, but fieldsToRemove only removes manager when contactManager is truthy. With an empty string, the conflict remains. Align this with the same condition used for unset.

🐛 Proposed fix
-		const fieldsToRemove = new Set<string>(
-			[
-				name && 'name',
-				contactManager && 'manager',
+		const fieldsToRemove = new Set<string>(
+			[
+				name && 'name',
+				('contactManager' in params) && 'manager',
 				...(customFields ? Object.keys(customFields).map((key) => `customFields.${key}`) : []),
 			].filter((field): field is string => !!field),
 		);
🤖 Fix all issues with AI agents
In `@apps/meteor/app/livechat/server/lib/contacts/updateContact.ts`:
- Around line 74-85: The patchContact call is including name even when
undefined, which can overwrite existing names; update the
LivechatContacts.patchContact invocation in updateContact.ts to only add name to
the set object when params.name is explicitly provided (e.g., check for
params.hasOwnProperty('name') or typeof name !== 'undefined') so that the set
payload omits name when not present; keep the other conditional spreads (emails,
phones, contactManager, channels, customFieldsToUpdate, wipeConflicts) as-is and
retain the existing unset logic for contactManager.
🧹 Nitpick comments (1)
apps/meteor/tests/end-to-end/api/livechat/contacts.ts (1)

795-836: Drop inline comments in test body.

Guideline asks to avoid code comments in implementation; these can be removed without losing intent.

♻️ Proposed cleanup
-			// Create a conflict
 			await request.post(api('livechat/custom.field')).send({ token, key: 'cf1', value: '111', overwrite: true }).expect(200);

 			await request.post(api('livechat/custom.field')).send({ token, key: 'cf1', value: '222', overwrite: false }).expect(200);

-			// Verify the contact has a contact manager and conflicts
 			const contactBefore = await request.get(api('omnichannel/contacts.get')).set(credentials).query({ contactId }).expect(200);
As per coding guidelines, avoid code comments in implementation.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/meteor/app/livechat/server/lib/contacts/updateContact.ts">

<violation number="1" location="apps/meteor/app/livechat/server/lib/contacts/updateContact.ts:76">
P2: Using a truthy check here skips updates when `name` is an empty string, but downstream logic still treats `name` as a change and updates rooms/subscriptions. This can leave the contact name unchanged while related data is cleared. Prefer checking for undefined so empty strings are persisted consistently.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@alfredodelfabro alfredodelfabro added this to the 8.2.0 milestone Jan 22, 2026
abhinavkrin and others added 10 commits January 28, 2026 18:07
Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
@abhinavkrin abhinavkrin force-pushed the fix/clear-contact-manager-field branch from 264eaa1 to 2a05f70 Compare January 28, 2026 12:39
@abhinavkrin abhinavkrin requested a review from KevLehman January 28, 2026 12:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/app/livechat/server/lib/contacts/updateContact.spec.ts (1)

15-35: Align proxyquire stub path with the actual import specifier.

updateContact.ts imports from './patchContact' (no extension), but the test stubs './patchContact.ts' (with extension). The proxyquire specifiers must match exactly; a mismatch causes the stub to be ignored.

✅ Proposed fix
-const { patchContact } = proxyquire.noCallThru().load('./patchContact.ts', {
+const { patchContact } = proxyquire.noCallThru().load('./patchContact', {
 	'@rocket.chat/models': modelsMock,
 });

 ...
-	'./patchContact.ts': {
+	'./patchContact': {
 		patchContact,
 	},
🤖 Fix all issues with AI agents
In
`@apps/meteor/app/livechat/server/lib/contacts/resolveContactConflicts.spec.ts`:
- Around line 18-30: The proxyquire stubs use a different module specifier than
production so the dependency isn't intercepted: change both occurrences where
proxyquire.noCallThru().load is given './patchContact.ts' to use the exact same
specifier as the real import ('./patchContact') so the stub for patchContact
(used when loading resolveContactConflicts via resolveContactConflicts)
correctly overrides the module; update the string keys passed to proxyquire for
both the standalone patchContact load and the resolveContactConflicts stub
mapping.

In `@apps/meteor/app/livechat/server/lib/contacts/updateContact.spec.ts`:
- Around line 61-69: The test passes null for contactManager but the API
contract expects a string to clear the field; update the test call to
updateContact to use an empty string ('') for contactManager (reference the test
case invoking updateContact and the assertions on
modelsMock.LivechatContacts.patchContact) so the payload matches the params type
and represents the intended “clear” value.

Signed-off-by: Abhinav Kumar <[email protected]>
Signed-off-by: Abhinav Kumar <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants